package fr.uvsq.isty.bdh.hchess.engine;

import fr.uvsq.isty.bdh.hchess.gui.game.BoardDisplay;
import fr.uvsq.isty.bdh.hchess.interfacing.FailedToReachPlayerException;
import fr.uvsq.isty.bdh.hchess.interfacing.GameEvent;
import fr.uvsq.isty.bdh.hchess.interfacing.GameEventRefereeNotifier;
import fr.uvsq.isty.bdh.hchess.interfacing.IDisposableChessPlayer;
import ictk.boardgame.chess.ChessBoard;

/**
 * An implementation of an IChessPlayer that allow a human to play.
 * It register a MouseListener and a MouseMotionListener to the board each time 
 * he have to provide a move and unregister them after getting his move
 * @author Rémi Dubois <RemiEric.Dubois@gmail.com>
 */
public class HumanPlayer implements IDisposableChessPlayer {
    
    private int originX, originY;
    private int destinationX, destinationY;
    private boolean promotion;
    
    private BoardDisplay boardDisplay;
    private GameData gameData;
    private PieceMoveListener pieceMoveListener;
    private boolean isBlackPlayer;
    
    private boolean isClosed;
    
    private String playerName;
    
    private GameEventRefereeNotifier notifier;
    
    private boolean moveChosen;
    
    /**
     * 
     * @param game le GameData du jeu
     * @param name Le nom du joueur
     */
    public HumanPlayer(GameData game, String name) {
        boardDisplay = null;
        gameData = game;
        
        playerName = name;
        isClosed = true;
    }
    
    /**
     * Called by the gameData to tell wich valid move the player have chosen
     * @param originFile the file of the origin of the move 
     * @param originRank the rank of the origin of the move
     * @param destinationFile the file of the destination of the move
     * @param destinationRank the rank of the destination of the move
     * @param promotion true if there is a promotion to apply during this move 
     */
    synchronized public void setMove(int originFile, int originRank, int destinationFile, int destinationRank, boolean promotion) {
        
        this.originX = originFile;
        this.originY = originRank;
        this.destinationX = destinationFile;
        this.destinationY = destinationRank;
        this.promotion = promotion;
        this.moveChosen = true;
        notify();
    }

    /**
     * Currently not implemented, just call the version without timerLimit
     * @param lastMove the last move played
     * @param timeLimit the time to play
     * @return the move chosen by the player
     */
    public int[] getNextMove(int[] lastMove, int timeLimit) throws FailedToReachPlayerException {
        return getNextMove(lastMove);
    }

    /**
     * not implemented
     */
    public boolean sendsChat() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    /**
     * not implemented
     */
    public boolean recivesChat() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    /**
     * not implemented
     * @param message
     * @throws NotImplementedException 
     */
    public void notifyChatRecived(String message) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    /**
     * not implemented
     */
    public String getChat() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    /**
     * not implemented
     */
    public boolean handlesNonStandardStarts() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    /**
     * connect listeners to the display, get the player's move and return it
     * @param lastMove the last move played
     * @return the move of the player
     */
    synchronized public int[] getNextMove(int[] lastMove) throws FailedToReachPlayerException {
        
        int [] move;
        
        moveChosen = false;
        
        boardDisplay.addMouseListener(pieceMoveListener);
        boardDisplay.addMouseMotionListener(pieceMoveListener);
        
        while(!moveChosen) {
            
            if(isClosed){
                boardDisplay.removeMouseListener(pieceMoveListener);
                boardDisplay.removeMouseMotionListener(pieceMoveListener);
                throw new FailedToReachPlayerException("Player was closed");
            }
            
            try {
                wait(100);
            } catch (InterruptedException ex) {
            }
        }
        
        if(promotion) {
            move = new int[5];
            move[0] = originX;
            move[1] = originY;
            move[2] = destinationX;
            move[3] = destinationY;
            move[4] = gameData.getPromotion();
        }
        else {
            move = new int[4];
            move[0] = originX;
            move[1] = originY;
            move[2] = destinationX;
            move[3] = destinationY;
        }
        
        boardDisplay.removeMouseListener(pieceMoveListener);
        boardDisplay.removeMouseMotionListener(pieceMoveListener);
        
        return move;
    }

    /**
     * Do nothing
     * @param lastMove the last move played
     */
    public void notifyFinalMove(int[] lastMove) {
        // Game Over
    }

    /**
     * Do nothing
     * @param event 
     */
    public void notifyGameEvent(GameEvent event) {
    }

    /**
     * Create the PieceMoveListener he need to get the player's inputs
     * @param board the board on wich we will get the player's inputs
     * @param game the gameData to use
     * @param isBlackPlayer tell if the player is the black player 
     */
    void connectToDisplay(BoardDisplay board, boolean isBlackPlayer) {
        boardDisplay = board;
        this.isBlackPlayer = isBlackPlayer;
        pieceMoveListener = new PieceMoveListener(this, boardDisplay, gameData);
    }

    /**
     * Tell if that player is the black player
     * @return true if the player is the black player
     */
    public boolean isBlackPlayer() {
        return isBlackPlayer;
    }

    /**
     * Do nothing
     * @return true
     */
    public boolean handlesResume() {
        return true;
    }

    /**
     * Do nothing
     * @param notifier
     * @param startPos
     * @param stateToRestore
     * @throws FailedToReachPlayerException 
     */
    public void setUp(
            GameEventRefereeNotifier notifier,
            ChessBoard startPos,
            int[][] stateToRestore)
                throws FailedToReachPlayerException {
        // Let's go.
        this.notifier = notifier;
        isClosed = false;
    }
    
    public void sendEventToReferee(GameEvent e) {
        notifier.notifyEvent(e);
    }

    public void close() {
        isClosed = true;
    }
    
    /**
     * @return The name of the player.
     */
    public String getName(){
    	return playerName;   	
    }
}
